﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Net.Mail;
using SsrsRenderStudio.Configuration;
using SsrsRenderStudio.KnownTypesAndModels;
using System.Resources;

namespace SsrsRenderStudio.Shared
{
    public static class Logger
    {
        private class LogQueueObject
        {
            public DateTime Stamp;
            public EnumLoggingSeverity Severity;
            public string SessionId;
            public string MessageId;
            public string Message;
            
            public LogQueueObject(EnumLoggingSeverity severity, string sessionId, string messageId, string message)
            {
                Stamp = DateTime.Now;
                Severity = severity;
                Message = message;
                SessionId = sessionId;
                MessageId = messageId;
            }
        }

        private static string _appName = "?APPNAME?";
        private static string _logSourceName = "?SOURCE?";
        private static int _loggingVerbosity = (int)EnumLoggingSeverity.ERROR;
        private static Dictionary<EnumLoggingSeverity, List<LoggingAction>> _loggingActionsToExecute = null;

        private static Queue<LogQueueObject> _queue = new Queue<LogQueueObject>();  // queue for queued logging
        private static System.Timers.Timer _logTimer = new System.Timers.Timer();   // deque timer
        private static TimeSpan _logInterval = new TimeSpan(0, 0, 30);              // every 30 secs by default
        private static bool _immediateLogging = false;                              // default to false to catch the first logs

        private static SmtpInfo _smtpInfo = null;                                   // email server info
        private static SmtpClient _smtpClient = null;                               // sending client

        private static Dictionary<int, string> _sessions = null;                    // sessions cache to aid in logging by session due to the logger's static class type
        public static Dictionary<string, Stopwatch> _timers = null;                 // performance section timers

        static Logger()
        {
            // init variables
            _loggingActionsToExecute = new Dictionary<EnumLoggingSeverity, List<LoggingAction>>();
            _timers = new Dictionary<string, Stopwatch>();
            _sessions = new Dictionary<int, string>();

            StartSession(Properties.Resources.LOGGER_SESSION_ID_DEFAULT);           // start default session

            prepareFacility();
            startFacility();
        }

        public static void DestroyLogger()
        {
            EndSession();
        }

        private static void prepareFacility()
        {
            RenderStudioObjectConfig10 appInfo = null;
            LoggingConfig loggingConfig = null;

            try
            {
                ApplicationConfig appconfig = SsrsRenderStudio.Configuration.ConfigurationManager.ApplicationConfigFromConfigPointer;

                if (appconfig.IsServer)
                    appInfo = SsrsRenderStudio.Configuration.ConfigurationManager.ServerInfoFromFriendlyName10(appconfig.ApplicationName);
                else if (appconfig.IsClient)
                    appInfo = SsrsRenderStudio.Configuration.ConfigurationManager.ClientInfoFromFriendlyName10(appconfig.ApplicationName);
                loggingConfig = ConfigurationManager.LoggingConfigForSource(appInfo.LoggingSource);

                // build all logging actions
                foreach (EnumLoggingSeverity enumSeverity in Enum.GetValues(typeof(EnumLoggingSeverity)))
                {
                    List<LoggingAction> loggingActions = ConfigurationManager.LoggingActionsForSource(loggingConfig.SourceName, enumSeverity);
                    if (loggingActions != null && loggingActions.Count > 0)
                        _loggingActionsToExecute.Add(enumSeverity, loggingActions);
                }

                _smtpInfo = ConfigurationManager.SmtpSettings();
            }
            catch
            {
                WriteConsole("FAILED TO CONFIGURE LOGGING FACILITY!");
            }

            try
            {
                _appName = appInfo.FriendlyName;
                _logSourceName = loggingConfig.SourceName;
                _loggingVerbosity = loggingConfig.LoggingVerbosity;
                _logInterval = loggingConfig.LogProcessingInterval;
                _immediateLogging = loggingConfig.IsImmediate;
                _smtpClient = new SmtpClient(_smtpInfo.SmtpHost);
            }
            catch { }
        }

        private static void startFacility()
        {
            if (_loggingActionsToExecute != null && _loggingActionsToExecute.Count > 0 && !_immediateLogging)
            {
                _logTimer.Interval = _logInterval.TotalMilliseconds;
                _logTimer.Elapsed += new System.Timers.ElapsedEventHandler(_logTimer_Elapsed);
                _logTimer.AutoReset = true;
                _logTimer.Start();
            }
            else
            {
                processLogQueue(); // process any logs that might have come in during the ctor.
            }
        }

        private static void _logTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            processLogQueue();
        }

        private static void processLogQueue()
        {
            while (_queue.Count > 0)
            {
                processLogObject(_queue.Dequeue());
            }
        }

        private static void processLogObject(LogQueueObject logObject)
        {
            if ((int)logObject.Severity > _loggingVerbosity) return;

            List<LoggingAction> loggingActions = null;
            _loggingActionsToExecute.TryGetValue(logObject.Severity, out loggingActions);

            if (loggingActions == null) return;

            string fullMessage = string.Format(Properties.Resources.LOGGER_MESSAGE_TEMPLATE, logObject.Stamp, logObject.Severity.ToString(), _logSourceName, _appName, logObject.SessionId, logObject.MessageId, logObject.Message);
           
            foreach (LoggingAction action in loggingActions)
            {
                switch (action.Action)
                {
                    case EnumLoggingAction.CONSOLE:
                        WriteConsole(fullMessage);
                        break;
                    case EnumLoggingAction.EMAIL:
                        LogToMail(fullMessage, action.Parameters.Table["Recepient"].ToString());
                        break;
                    case EnumLoggingAction.EVENT:
                        LogToEventLog(logObject.Severity, fullMessage, action.Parameters.Table["Logname"].ToString(), action.Parameters.Table["Source"].ToString());
                        break;
                    case EnumLoggingAction.LOG:
                        LogToFile(fullMessage, action.Parameters.Table["Filename"].ToString());
                        break;
                    case EnumLoggingAction.NONE:
                        break;
                }
            }

            return;
        }

        #region sessions

        public static void StartSession(string sessionId)
        {
            _sessions.Add(System.Threading.Thread.CurrentThread.ManagedThreadId, sessionId);
        }

        public static void EndSession()
        {
            string session = getCurrentSessionName();

            foreach (string k in _timers.Keys)
            {
                if (k.StartsWith(session))
                {
                    Log("40002", k, session);

                    try
                    {
                        _timers[k].Stop();
                        _timers.Remove(k);
                    }
                    catch { }
                }
            }

            _sessions.Remove(System.Threading.Thread.CurrentThread.ManagedThreadId);
        }

        #endregion

        #region logging handlers

        public static void LogSection(string MessageId, string sectionName, params object[] parameters)
        {
            TimeSpan time = StopSection(sectionName);

            verifyTrace(sectionName, time);

            if (parameters == null || parameters.Length == 0)
            {
                Log(MessageId, time.TotalMilliseconds);
            }
            else
            {
                List<object> p = parameters.ToList();
                p.Add(time.TotalMilliseconds);
                Log(MessageId, p.ToArray());

            }
        }

        public static void Log(string messageId, params object[] parameters)
        {
            string fullMessageId = messageId.ToUpper().StartsWith(Properties.Resources.LOGGER_MESSAGE_PREFIX) ? messageId : Properties.Resources.LOGGER_MESSAGE_PREFIX + messageId;

            try
            {
                 string msgFormat = Properties.Resources.ResourceManager.GetString(fullMessageId);
                string msg = string.Format(msgFormat, parameters);

                EnumLoggingSeverity severity = EnumLoggingSeverity.WARNING;
                try { severity = (EnumLoggingSeverity)int.Parse(messageId.Replace(Properties.Resources.LOGGER_MESSAGE_PREFIX, "").Substring(0, 1)); }
                catch { }

                Log(severity, fullMessageId,  msg);
            }
            catch(Exception ex)
            {
                Log("10009", fullMessageId, (parameters == null || parameters.Length == 0) ? "n/a" : string.Join(";", parameters) );
            }
        }

        public static void Log(EnumLoggingSeverity severity, string message)
        {
            if (_immediateLogging)
                processLogObject(new LogQueueObject(severity, getCurrentSessionName(), Properties.Resources.LOGGER_UNKNOWN_MESSAGE_ID, message));
            else
                _queue.Enqueue(new LogQueueObject(severity, getCurrentSessionName(), Properties.Resources.LOGGER_UNKNOWN_MESSAGE_ID, message));

            return;
        }

        public static void Log(EnumLoggingSeverity severity, string messageId, string message)
        {
            if (_immediateLogging)
                processLogObject(new LogQueueObject(severity, getCurrentSessionName(), messageId, message));
            else
                _queue.Enqueue(new LogQueueObject(severity, getCurrentSessionName(), messageId, message));
            
            return;
        }

        #endregion

        #region action type handlers

        public static void WriteConsole(string message)
        {
            if (Environment.UserInteractive)
                Console.WriteLine(message);
        }

        private static void LogToEventLog(EnumLoggingSeverity severity, string eventMessage, string logName, string sourceName)
        {
            EventLogEntryType entryType = EventLogEntryType.Information;

            if ((int)severity < (int)EnumLoggingSeverity.WARNING) 
                entryType = EventLogEntryType.Error;
            else if ((int)severity < (int)EnumLoggingSeverity.INFORMATIONAL) 
                entryType = EventLogEntryType.Warning;

            try
            {
                if (!EventLog.SourceExists(sourceName)) EventLog.CreateEventSource(sourceName, logName);
                EventLog.WriteEntry(sourceName, eventMessage, entryType, 100 + (int)severity);
            }
            catch(Exception ex) 
            {
                WriteConsole("Logging: LogToEventLog: " + ex.Message); 
            }
        }

        private static void LogToMail(string eventMessage, string recepient)
        {
            try
            {
                MailMessage mail = new MailMessage();

                mail.From = new MailAddress(_smtpInfo.FromAddress, _smtpInfo.FromDisplayName);
                mail.To.Add(recepient);
                mail.Subject = "Message from " + _appName;
                mail.Body = eventMessage;
                mail.IsBodyHtml = false;

                _smtpClient.Send(mail);

                mail = null;
            }
            catch (Exception ex)
            {
                WriteConsole("Logging: LogToMail: " + ex.Message);
            }
        }

        private static void LogToFile(string eventMessage, string filename)
        {
            try
            {
                System.IO.StreamWriter sw = System.IO.File.AppendText(ConfigurationManager.GetCurrentDirectory() + @"\" + filename);
                sw.WriteLine(eventMessage);
                sw.Close();
                sw.Dispose();
                sw = null;
            }
            catch (Exception ex)
            {
                WriteConsole("Logging: LogToFile: " + ex.Message);
            }

        }

        #endregion

        #region performance sections

        private static void verifyTrace(string sectionName, TimeSpan executionTime)
        {
            if (!bool.Parse(Properties.Resources.LOGGER_TRACE_ENABLED))
                return;

            if (sectionName.ToUpper().StartsWith(Properties.Resources.LOGGER_TRACE_PREFIX))
            {
                try
                {
                    int limitMs = int.Parse(Properties.Resources.ResourceManager.GetString(sectionName));
                    if (executionTime.TotalMilliseconds >= limitMs)
                    {
                        Log(Properties.Resources.LOGGER_TRACE_MESSAGE_TEMPLATE, sectionName, executionTime.TotalMilliseconds, limitMs);
                    }
                }
                catch
                {
                    Log("40001", sectionName);
                }
            }
        }

        public static void StartSection(string name, EnumLoggingSeverity severity = EnumLoggingSeverity.DEBUGVERBOSE)
        {
            if (!canLog(severity))
                return;

            _timers.Add(getUniqueTimerName(name), new Stopwatch());
            _timers[getUniqueTimerName(name)].Start();
        }

        public static TimeSpan StopSection(string name, EnumLoggingSeverity severity = EnumLoggingSeverity.DEBUGVERBOSE)
        {
            if (!canLog(severity))
                return new TimeSpan(-1);

            TimeSpan ts;
            _timers[getUniqueTimerName(name)].Stop();
            ts = _timers[getUniqueTimerName(name)].Elapsed;
            _timers.Remove(getUniqueTimerName(name));
            return ts;
        }

        #endregion

        private static bool canLog(EnumLoggingSeverity requestedSeverity)
        {
            return (int)requestedSeverity <= _loggingVerbosity;
        }

        private static string getUniqueTimerName(string sectionName)
        {
            return getCurrentSessionName() + "," + sectionName;
        }

        private static string getCurrentSessionName()
        {
            if(_sessions.ContainsKey(System.Threading.Thread.CurrentThread.ManagedThreadId))
                return _sessions[System.Threading.Thread.CurrentThread.ManagedThreadId];
            else
                return string.Format(Properties.Resources.LOGGER_SESSION_ID_UNKNOWN_TEMPLATE, System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
        }
    } 
}
